package com.beyondsoft.common.activiti.support;

import com.beyondsoft.common.activiti.exception.ActivitiException;
import com.beyondsoft.common.activiti.exception.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.image.ProcessDiagramGenerator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author zhangyan
 * @version $ Id: ShowProcessDiagramSupport.java, v 0.1 2020/6/20 11:47 zhangyan Exp $
 */
@Component
@Slf4j
public class ShowProcessDiagramSupport {

    private static final String DEFAULT_IMAGE_TYPE = "png";
    private static final String DEFAULT_ACTIVITY_FONT_NAME = "宋体";
    private static final String DEFAULT_LABLE_FONT_NAME = "宋体";
    private static final String DEFAULT_ANNOTATION_FONT_NAME = "宋体";
    @Resource
    private ProcessEngine processEngine;

    public byte[] showByte(String instanceId) {
        ByteArrayOutputStream baos = null;
        byte[] diagramByte;
        try {
            baos = new ByteArrayOutputStream();
            this.show(instanceId, baos);
            diagramByte = baos.toByteArray();
        } catch (Exception e) {
            log.info("读取流程图失败", e);
            throw new ActivitiException(ResultCode.NO_DATA.getCode(), "流程跟踪图片生成失败");
        } finally {
            try {
                if (baos != null) {
                    baos.close();
                }
            } catch (Exception e) {
                log.info("Failure to close the outputStream", e);
            }
        }
        return diagramByte;
    }

    public void show(String instanceId, OutputStream outputStream) {
        InputStream imageStream = null;
        try {
            imageStream = this.show(instanceId);
            byte[] b = new byte[1024];
            int len;
            while ((len = imageStream.read(b, 0, b.length)) != -1) {
                outputStream.write(b, 0, len);
            }
            outputStream.flush();
        } catch (Exception e) {
            throw new ActivitiException(ResultCode.NO_DATA.getCode(), "流程跟踪图片生成失败");
        } finally {
            try {
                if (imageStream != null) {
                    imageStream.close();
                }
            } catch (Exception e) {
                log.info("Failure to close the inputStream");
            }

        }
    }

    public InputStream show(String instanceId) {
        if (StringUtils.isBlank(instanceId)) {
            throw new ActivitiException(ResultCode.NO_DATA.getCode(), "流程实例编号不能为空");
        }
        HistoricProcessInstance processInstance = processEngine.getHistoryService()
                .createHistoricProcessInstanceQuery()
                .processInstanceId(instanceId)
                .singleResult();
        if (processInstance == null) {
            throw new ActivitiException(ResultCode.NO_DATA.getCode(), "流程实例不存在");
        }
        List<HistoricActivityInstance> historicActivityInstanceList =
                this.processEngine.getHistoryService()
                        .createHistoricActivityInstanceQuery()
                        .processInstanceId(instanceId)
                        .orderByHistoricActivityInstanceId()
                        .asc()
                        .list();
        BpmnModel bpmnModel = this.processEngine.getRepositoryService().getBpmnModel(processInstance.getProcessDefinitionId());
        InputStream inputStream;
        if (CollectionUtils.isEmpty(historicActivityInstanceList)) {
            inputStream = this.genDiagram(bpmnModel, null, null);
        } else {
            List<String> highLightedActivities = this.getHighLightedActivities(historicActivityInstanceList);
            List<String> highLightedFlows = this.getHighLightedFlows(bpmnModel, historicActivityInstanceList);
            inputStream = this.genDiagram(bpmnModel, highLightedActivities, highLightedFlows);
        }
        return inputStream;
    }

    private List<String> getHighLightedActivities(List<HistoricActivityInstance> historicActivityInstanceList) {
        return historicActivityInstanceList.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toList());
    }

    private List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstanceList) {
        List<String> highLightedFlowIds = new ArrayList();
        for (int i = 0; i < historicActivityInstanceList.size() - 1; ++i) {
            HistoricActivityInstance activityimpl_ = historicActivityInstanceList.get(i);
            List<FlowNode> sameStartTimeNodes = new ArrayList();
            FlowNode sameActivityImpl = getNextNode(bpmnModel, activityimpl_, historicActivityInstanceList, i);
            if (sameActivityImpl != null) {
                sameStartTimeNodes.add(sameActivityImpl);
            }
            for (int j = i + 1; j < historicActivityInstanceList.size() - 1; ++j) {
                HistoricActivityInstance activityImpl1 = historicActivityInstanceList.get(j);
                HistoricActivityInstance activityImpl2 = historicActivityInstanceList.get(j + 1);
                if (activityImpl1.getStartTime().getTime() != activityImpl2.getStartTime().getTime()) {
                    break;
                }

                FlowNode sameActivityImpl2 = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityImpl2.getActivityId());
                sameStartTimeNodes.add(sameActivityImpl2);
            }

            FlowNode activityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement((historicActivityInstanceList.get(i)).getActivityId());
            List<SequenceFlow> pvmTransitions = activityImpl.getOutgoingFlows();
            Iterator var15 = pvmTransitions.iterator();

            while (var15.hasNext()) {
                SequenceFlow pvmTransition = (SequenceFlow) var15.next();
                FlowNode pvmActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(pvmTransition.getTargetRef());
                if (sameStartTimeNodes.contains(pvmActivityImpl)) {
                    highLightedFlowIds.add(pvmTransition.getId());
                }
            }
        }

        return highLightedFlowIds;
    }

    private static FlowNode getNextNode(BpmnModel bpmnModel, HistoricActivityInstance activityImpl, List<HistoricActivityInstance> historicActivityInstanceList, int index) {
        FlowNode sameActivityImpl = null;
        if (!"userTask".equals(activityImpl.getActivityType())) {
            if (index != historicActivityInstanceList.size()) {
                sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement((historicActivityInstanceList.get(index + 1)).getActivityId());
            }

            return sameActivityImpl;
        } else {
            for (int k = index + 1; k <= historicActivityInstanceList.size() - 1; ++k) {
                HistoricActivityInstance activityImpl2 = historicActivityInstanceList.get(k);
                if (!"userTask".equals(activityImpl2.getActivityType()) || activityImpl.getStartTime().getTime() != activityImpl2.getStartTime().getTime()) {
                    sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement((historicActivityInstanceList.get(k)).getActivityId());
                    break;
                }
            }

            return sameActivityImpl;
        }
    }

    private InputStream genDiagram(BpmnModel bpmnModel, List<String> highLightedActivities, List<String> highLightedFlows) {
        ProcessDiagramGenerator processDiagramGenerator = this.processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
        return processDiagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivities, highLightedFlows, "宋体", "宋体", "宋体", (ClassLoader) null, 2.0D);
    }
}
